import { isReserveTag } from "../../utils/utils.js"
import Vue from "../index.js"


export function patch(oldVnode, vnode) {
  // 存在旧节点，没有新节点，则销毁组件
  if (oldVnode && !vnode) return
  if (!oldVnode) {
    // 不存在旧节点，说明子组件首次渲染
    createElm(vnode)
  } else {
    if (oldVnode.nodeType) {
      // 如果是真实节点，则表示首次渲染根组件
      // 父节点
      const parent = oldVnode.parentNode
      // 参看节点，即老的vnode的下一个节点 ———— script，新节点要插在script前面
      const referNode = oldVnode.nextSibling
      // 创建元素
      createElm(vnode, parent, referNode)
      // 移除旧的vnode
      parent.removeChild(oldVnode)
    } else {
      // 后续更新, 对比新旧vnode，开始diff
      patchVnode(oldVnode, vnode)
    }
  }
  return vnode.elm
}

/**
 * 创建元素
 * @param {*} vnode VNode
 * @param {*} parent VNode 的父节点，真实节点
 * @param {*} referNode 参考节点（老的vnode的下一个节点）
 */
function createElm(vnode, parent, referNode) {
  if (!parent) {
  }
  // 记录节点的父节点
  vnode.parent = parent
  // 创建自定义组件，如果是非组件，则会继续往下
  if (createComponent(vnode)) return

  const { attr, children, text } = vnode
  if (text) {
    // 文本节点, 创建并插入到该点上
    vnode.elm = createTextNode(vnode)
  } else {
    // 元素节点， 创建并插入
    vnode.elm = document.createElement(vnode.tag)
    // 给元素设置属性
    setAttribute(attr, vnode)
    // 递归创建子节点
    for (let i = 0; i < children.length; i++) {
      if(children[i]) {
        createElm(children[i], vnode.elm)
      }
    }
  }
  // 如果存在parent, 则将创建的节点插入到父节点内
  if (parent) {
    const elm = vnode.elm
    if (referNode) {
      parent.insertBefore(elm, referNode)
    } else {
      parent.appendChild(elm)
    }
  }
}
/**
 * 创建文本节点
 * @param {*} textVNode 文本节点的 VNode 
 * @returns 真实的文本节点
 */
function createTextNode(textVNode) {
  let { text } = textVNode,
    textNode = null
  if (text.expression) {
    // 存在表达式，这个表达式的值是一个响应式数据
    const value = textVNode.context[text.expression]
    textNode = document.createTextNode(typeof value === 'object' ? JSON.stringify(value) : String(value))
  } else {
    // 只有文本信息
    textNode = document.createTextNode(text.text)
  }
  return textNode
}

/**
 * 给节点设置属性
 * @param {*} attr 属性map对象 
 * @param {*} vnode 
 */
function setAttribute(attr, vnode) {
  for (let name in attr) {
    // 遍历属性，如果是普通属性，直接设置，特殊指令特殊处理
    switch (name) {
      case 'vModel':
        const { tag, value } = attr.vModel
        setVModel(tag, value, vnode)
        break
      case 'vBind':
        setVBind(vnode)
        break
      case 'vOn':
        setVOn(vnode)
        break
      default:
        vnode.elm.setAttribute(name, attr[name])
        break
    }
  }
}

/**
 * 设置v-model属性
 * @param {*} tag  标签名
 * @param {*} value 属性值
 * @param {*} vnode 节点
 */
function setVModel(tag, value, vnode) {
  const { context: vm, elm } = vnode
  if(tag === 'select') {
    // 通过微任务来把设置值延迟，因为此时option元素还没创建
    Promise.resolve().then(() => {
      elm.value = vm[value]
    })
    // 输入时更新
    elm.addEventListener('change', () => {
      vm[value] = elm.value
    })
  } else if(tag === 'input' && vnode.elm.type === 'text') {
    elm.value = vm[value]
    elm.addEventListener('input', function() {
      vm[value] = elm.value
    })
  } else if(tag === 'input' && vnode.elm.type === 'checkbox') {
    elm.checked = vm[value]
    elm.addEventListener('change', function() {
      vm[value] = elm.checked
    })
  }
}
/**
 * 设置v-bind属性
 * @param {*} vnode 
 */
function setVBind(vnode) {
  const { attr: { vBind }, elm, context: vm } = vnode
  // 这些值全部都在vm上，所以直接在vm中拿到并设置在元素上
  for(let attrName in vBind) {
    elm.setAttribute(attrName, vm[vBind[attrName]])
  }
}
/**
 * v-on
 * @param {*} vnode 
 */
function setVOn(vnode) {
  const { attr: { vOn }, elm, context: vm } = vnode
  for(let eventName in vOn) {
    // 动态绑定事件
    elm.addEventListener(eventName, function(...args) {
      vm.$options.methods[vOn[eventName]].apply(vm, args)
    })
  }
}
/**
 * 创建自定义组件
 * @param {*} vnode 
 */
function createComponent(vnode) {
  if (vnode.tag && !isReserveTag(vnode.tag)) {
    // 非保留节点，则说明是组件
    // 获取组件配置
    const { tag, context: { $options: { components } } } = vnode
    const compOptions = components[tag]
    const compIns = new Vue(compOptions)
    compIns._parentVnode = vnode
    // 挂载子组件
    compIns.$mount()
    // 记录子组件VNode的父节点信息
    compIns._vnode.parent = vnode.parent
    // 将子组件添加到父节点内
    compIns._vnode.elm && vnode.parent.appendChild(compIns._vnode.elm)
    return true
  }
}

/**
 * 对比新旧节点，找出不同，更新旧节点
 * @param {*} oldVnode 
 * @param {*} vnode 
 */
function patchVnode(oldVnode, vnode) {
  // 如果相同，直接结束
  if (oldVnode === vnode) return
  // 将老vnode 上的真实节点同步更新到新的vnode上， 否则后续更新时会出现vnode.elm为空
  vnode.elm = oldVnode.elm
  // 进一步比较子节点
  const ch = vnode.children
  const oldCh = oldVnode.children
  // 新节点不存在文本
  if (!vnode.text) {
    // 说明新老节点都还有子节点
    if(ch && oldCh) {
      // 进入diff
      updateChildren(ch, oldCh)
    } else if(ch) {
      // 没有旧节点

    } else {
      // 新节点没有子节点，旧节点有
      // 删除这些子节点
    }
  } else {
    // 新节点存在文本节点
    if(vnode.text.expression) {
      // 说明存在表达式 形如 {{ xxx }}
      // 获取表达式的新值
      const value = JSON.stringify(vnode.context[vnode.text.expression])
      try {
        // 获取旧值
        const oldValue = oldVnode.elm.textContent
        // 对比，不一样则更新, 此时是响应式的更新
        if (value !== oldValue) {
          oldVnode.elm.textContent = value
        }
      } catch {
        // 更新遇到插槽，导致报错
      }
    }
  }
}
/**
 * diff, 对比孩子节点，找出不同，将不同点更新到旧节点上
 * @param {*} ch 新的vnode 的所有子节点
 * @param {*} oldCh 旧的vnode 的所有子节点
 */
function updateChildren(ch, oldCh) {
  console.log(ch, oldCh)
  // 新节点的索引----开始
  let newStartIdx = 0
  // 新节点的索引----结束
  let newEndIdx = ch.length - 1
  // 旧节点----开始
  let oldStartIdx = 0
  // 旧节点----结束
  let oldEndIdx = oldCh.length - 1
  // 遍历新旧节点，找出节点不同点
  while(newStartIdx <= newEndIdx && oldStartIdx <= oldEndIdx) {
    // 根为web中的DOM操作特点，有4种假设，降低时间复杂度
    // 新的--开始节点
    const newStartNode = ch[newStartIdx]
    // 新的--结束节点
    const newEndNode = ch[newEndIdx]
    // 旧的--开始节点
    const oldStartNode = oldCh[oldStartIdx]
    // 旧的--结束节点
    const oldEndNode = oldCh[oldEndIdx]
    // 假设新旧开始节点是同一个
    if (sameVNode(newStartNode, oldStartNode)) {
      // 对比两个节点，找不同，更新
      patchVnode(oldStartNode, newStartNode)
      // 更新索引，进行下一轮的比较
      oldStartIdx++
      newStartIdx++
    } else if(sameVNode(newStartNode, oldEndNode)) {
      // 新开始和旧结束是同一个
      patchVnode(oldEndNode, newStartNode)
      // 将旧节点移动到新开始的位置
      oldEndNode.elm.parentNode.insertBefore(oldEndNode.elm, oldCh[newStartIdx].elm)
      // 更新索引
      newStartIdx++
      oldEndIdx--
    } else if (sameVNode(newEndNode, oldStartNode)) {
      // 新结束和旧开始是用一个
      patchVnode(oldStartNode, newEndNode)
      // 将旧的移动到新的位置
      oldStartNode.elm.parentNode.insertBefore(oldStartNode.elm, oldCh[newEndIdx].elm.nextSibling)
      // 更新索引
      newEndIdx--
      oldStartIdx++
    } else if (sameVNode(newEndNode, oldEndNode)) {
      // 新结束和旧结束是用一个
      patchVnode(oldEndNode, newEndNode)
      // 
      newEndIdx--
      oldEndIdx--
    } else {
      // 假设都不是以上4中情况，则暴力遍历来找到相同的那个元素
      console.log('暴力求解')
    }
    if (newStartIdx < newEndIdx) {
      // 说明旧节点先遍历结束，则将剩余的新节点添加到DOM中
    }
    if (oldStartIdx < oldEndIdx) {
      // 说明新节点遍历结束，将剩余的旧节点从DOM中删除
    }
  }
}
/**
 * 判断两个节点是否相同
 * @param {*} n1 
 * @param {*} n2 
 */
function sameVNode(n1, n2) {
  return n1.key == n2.key && n1.tag === n2.tag
}